有了前面的邏輯,就著使用Expression實作動態建立方法。
除了有能力動態建立方法,相比Emit有以下優點 :
可讀性好
,可用熟悉的關鍵字,像是變數Variable對應Expression.Variable、建立物件New對應Expression.New方便Runtime Debug
,可以在Debug模式下看到Expression對應邏輯代碼所以特別適合介紹動態方法建立,但Expression相比Emit無法作一些細節操作,這點會在後面Emit講解到。
邏輯 :
User 動態方法(IDataReader reader)
{
var user = new User();
var value = reader[0];
if( !(value is System.DBNull) )
user.Name = (string)value;
value = reader[1];
if( !(value is System.DBNull) )
user.Age = (int)value;
return user;
}
最後得出以下Exprssion版本代碼
public static class DemoExtension
{
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql) where T : new()
{
using (var command = cnn.CreateCommand())
{
command.CommandText = sql;
using (var reader = command.ExecuteReader())
{
var func = CreateMappingFunction(reader, typeof(T));
while (reader.Read())
{
var result = func(reader as DbDataReader);
yield return result is T ? (T)result : default(T);
}
}
}
}
private static Func<DbDataReader, object> CreateMappingFunction(IDataReader reader, Type type)
{
//1. 取得sql select所有欄位名稱
var names = Enumerable.Range(0, reader.FieldCount).Select(index => reader.GetName(index)).ToArray();
//2. 取得mapping類別的屬性資料 > 將index,sql欄位,class屬性資料做好對應封裝在一個變數內方便後面使用
var props = type.GetProperties().ToList();
var members = names.Select((columnName, index) =>
{
var property = props.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? props.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
return new
{
index,
columnName,
property
};
});
//3. 動態建立方法 : 從資料庫Reader按照順序讀取我們要的資料
/*方法邏輯 :
User 動態方法(IDataReader reader)
{
var user = new User();
var value = reader[0];
if( !(value is System.DBNull) )
user.Name = (string)value;
value = reader[1];
if( !(value is System.DBNull) )
user.Age = (int)value;
return user;
}
*/
var exBodys = new List<Expression>();
{
// 方法(IDataReader reader)
var exParam = Expression.Parameter(typeof(DbDataReader), "reader");
// Mapping類別 物件 = new Mapping類別();
var exVar = Expression.Variable(type, "mappingObj");
var exNew = Expression.New(type);
{
exBodys.Add(Expression.Assign(exVar, exNew));
}
// var value = defalut(object);
var exValueVar = Expression.Variable(typeof(object), "value");
{
exBodys.Add(Expression.Assign(exValueVar, Expression.Constant(null)));
}
var getItemMethod = typeof(DbDataReader).GetMethods().Where(w => w.Name == "get_Item")
.First(w => w.GetParameters().First().ParameterType == typeof(int));
foreach (var m in members)
{
//reader[0]
var exCall = Expression.Call(
exParam, getItemMethod,
Expression.Constant(m.index)
);
// value = reader[0];
exBodys.Add(Expression.Assign(exValueVar, exCall));
//user.Name = (string)value;
var exProp = Expression.Property(exVar, m.property.Name);
var exConvert = Expression.Convert(exValueVar, m.property.PropertyType); //(string)value
var exPropAssign = Expression.Assign(exProp, exConvert);
//if ( !(value is System.DBNull))
// (string)value
var exIfThenElse = Expression.IfThen(
Expression.Not(Expression.TypeIs(exValueVar, typeof(System.DBNull)))
, exPropAssign
);
exBodys.Add(exIfThenElse);
}
// return user;
exBodys.Add(exVar);
// Compiler Expression
var lambda = Expression.Lambda<Func<DbDataReader, object>>(
Expression.Block(
new[] { exVar, exValueVar },
exBodys
), exParam
);
return lambda.Compile();
}
}
}
查詢效果圖 :
最後查看Expression.Lambda > DebugView(注意是非公開屬性)驗證代碼 :
.Lambda #Lambda1<System.Func`2[System.Data.Common.DbDataReader,System.Object]>(System.Data.Common.DbDataReader $reader) {
.Block(
UserQuery+User $mappingObj,
System.Object $value) {
$mappingObj = .New UserQuery+User();
$value = null;
$value = .Call $reader.get_Item(0);
.If (
!($value .Is System.DBNull)
) {
$mappingObj.Name = (System.String)$value
} .Else {
.Default(System.Void)
};
$value = .Call $reader.get_Item(1);
.If (
!($value .Is System.DBNull)
) {
$mappingObj.Age = (System.Int32)$value
} .Else {
.Default(System.Void)
};
$mappingObj
}
}
-